/**
 * Copyright 1999-2015 dangdang.com.
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * </p>
 */
package spring.cloud.tasks.api.controller;


import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Pageable;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import spring.cloud.tasks.api.database.entity.TaskConfigurationDto;
import spring.cloud.tasks.api.domain.*;
import spring.cloud.tasks.api.service.AlarmStatisticsService;
import spring.cloud.tasks.api.service.TaskService;
import spring.cloud.tasks.api.utils.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * Job overview related operations.
 */
@RequestMapping("/console/namespaces/{namespace:.+}/jobs")
public class TaskController {

    private static final Logger log = LoggerFactory.getLogger(TaskController.class);

    private static final String QUERY_CONDITION_STATUS = "status";

    private static final String QUERY_CONDITION_GROUP = "groups";

    @Resource
    private TaskService taskService;

    @Resource
    private AlarmStatisticsService alarmStatisticsService;

    /**
     * 按条件分页获取域下所有作业的细节信息
     *
     * @param namespace 域名
     * @return 作业细节
     */
    //@ApiResponses(value = {@ApiResponse(code = 200, message = "Success/Fail", response = RequestResult.class)})
    @GetMapping
    public Response getJobsWithCondition(final HttpServletRequest request, @PathVariable String namespace,
                                         @RequestParam Map<String, Object> map, @RequestParam(required = false, defaultValue = "1") int page,
                                         @RequestParam(required = false, defaultValue = "25") int size) throws RuntimeException {
        if (map.containsKey(QUERY_CONDITION_STATUS)) {
            String statusStr = (String) map.get(QUERY_CONDITION_STATUS);
            TaskStatus jobStatus = TaskStatus.getJobStatus(statusStr);
            return Response.success(getJobOverviewByStatusAndPage(namespace, jobStatus, map, page, size));
        }
        return Response.success(getJobOverviewByPage(namespace, map, page, size));
    }

    /**
     * 获取域下的总作业数、启用作业数和异常作业数
     *
     * @param namespace 域名
     * @return 总作业数、启用作业数和异常作业数总数
     */
    //@ApiResponses(value = {@ApiResponse(code = 200, message = "Success/Fail", response = RequestResult.class)})
    @GetMapping(value = "/counts")
    public Response countJobsStatus(final HttpServletRequest request, @PathVariable String namespace)
            throws RuntimeException {
        return Response.success(countJobOverviewVo(namespace));
    }

    /**
     * 获取域下所有作业的名字
     *
     * @param namespace 域名
     * @return 全域作业名字
     */
    //@ApiResponses(value = {@ApiResponse(code = 200, message = "Success/Fail", response = RequestResult.class)})
    @GetMapping(value = "/names")
    public Response getJobNames(@PathVariable String namespace) throws RuntimeException {
        return Response.success(taskService.getTaskIdList(namespace));
    }

    //@ApiResponses(value = {@ApiResponse(code = 200, message = "Success/Fail", response = RequestResult.class)})
    @GetMapping(value = "/sharding/status")
    public Response getJobsShardingStatus(@PathVariable String namespace,
                                          @RequestParam(required = false) List<String> jobNames) throws RuntimeException {
        Map<String, String> jobShardingMap = new HashMap<>();
        if (!CollectionUtils.isEmpty(jobNames)) {
            for (String jobName : jobNames) {
                TaskStatus jobStatus = taskService.getTaskStatus(namespace, jobName);
                boolean isAllocated = !TaskStatus.STOPPED.equals(jobStatus) && taskService
                        .isJobShardingAllocatedExecutor(namespace, jobName);
                if (isAllocated) {
                    jobShardingMap.put(jobName, "已分配");
                } else {
                    jobShardingMap.put(jobName, "未分配");
                }
            }
        }
        return Response.success(jobShardingMap);
    }


    private JobOverviewVo getJobOverviewByPage(String namespace, Map<String, Object> condition, int page, int size)
            throws RuntimeException {
        JobOverviewVo jobOverviewVo = new JobOverviewVo();
        try {


            List<TaskConfigurationDto> unSystemJobs = taskService.getUnSystemJobsWithCondition(namespace, condition, page, size);
            if (unSystemJobs == null || unSystemJobs.isEmpty()) {
                jobOverviewVo.setJobOverviewJobVos(Lists.<JobOverviewJobVo>newArrayList());
                jobOverviewVo.setTotalNumber(0);
                return jobOverviewVo;
            }

            List<JobOverviewJobVo> jobOverviewList = updateJobOverviewDetail(namespace, unSystemJobs, null);

            jobOverviewVo.setJobOverviewJobVos(jobOverviewList);
            jobOverviewVo.setTotalNumber(taskService.countUnSystemJobsWithCondition(namespace, condition));
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        return jobOverviewVo;
    }

    private JobOverviewVo getJobOverviewByStatusAndPage(String namespace, TaskStatus jobStatus,
                                                        Map<String, Object> condition, int page, int size) throws RuntimeException {
        JobOverviewVo jobOverviewVo = new JobOverviewVo();
        try {
            preHandleStatusAndCondition(condition, jobStatus);

            List<TaskConfigurationDto> unSystemJobs = taskService.getUnSystemJobsWithCondition(namespace, condition, page, size);
            if (unSystemJobs == null || unSystemJobs.isEmpty()) {
                jobOverviewVo.setJobOverviewJobVos(Lists.<JobOverviewJobVo>newArrayList());
                jobOverviewVo.setTotalNumber(0);
                return jobOverviewVo;
            }

            Pageable pageable = PageableUtil.generatePageble(page, size);
            // 当 jobStatus 为null时，底层取数据已经做了分页，此处无需再次分页
            List<TaskConfigurationDto> targetJobs = jobStatus == null ? unSystemJobs : getJobSubListByPage(unSystemJobs, pageable);
            List<JobOverviewJobVo> jobOverviewList = updateJobOverviewDetail(namespace, targetJobs, jobStatus);

            jobOverviewVo.setJobOverviewJobVos(jobOverviewList);
            jobOverviewVo.setTotalNumber(taskService.countUnSystemJobsWithCondition(namespace, condition));
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        return jobOverviewVo;
    }


    private void preHandleStatusAndCondition(Map<String, Object> condition, TaskStatus jobStatus) {
        condition.put("jobStatus", jobStatus);
        if (TaskStatus.STOPPED.equals(jobStatus) || TaskStatus.STOPPING.equals(jobStatus)) {
            condition.put("isEnabled", SaturnConstants.JOB_IS_DISABLE);
        } else {
            condition.put("isEnabled", SaturnConstants.JOB_IS_ENABLE);
        }
    }

    private void updateAbnormalJobSizeInOverview(String namespace, JobOverviewVo jobOverviewVo) {
        try {
            List<AbnormalJob> abnormalJobList = alarmStatisticsService.getAbnormalJobListByNamespace(namespace);
            jobOverviewVo.setAbnormalNumber(abnormalJobList.size());
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    private List<JobOverviewJobVo> updateJobOverviewDetail(String namespace, List<TaskConfigurationDto> unSystemJobs,
                                                           TaskStatus jobStatus) {
        List<JobOverviewJobVo> result = Lists.newArrayList();
        for (TaskConfigurationDto jobConfig : unSystemJobs) {
            try {
                jobConfig.setDefaultValues();

                JobOverviewJobVo jobOverviewJobVo = new JobOverviewJobVo();
                BeanUtils.copyProperties(jobConfig, jobOverviewJobVo);

                updateJobTypesInOverview(jobConfig, jobOverviewJobVo);

                if (jobStatus == null) {
                    jobOverviewJobVo.setStatus(taskService.getTaskStatus(namespace, jobConfig));
                } else {
                    jobOverviewJobVo.setStatus(jobStatus);
                }
                result.add(jobOverviewJobVo);
            } catch (Exception e) {
                log.error("list job " + jobConfig.getTaskId() + " error", e);
            }
        }

        return result;
    }

    private void updateJobTypesInOverview(TaskConfiguration taskConfiguration, JobOverviewJobVo jobOverviewJobVo) {
        TaskType jobType = TaskType.getJobType(taskConfiguration.getTaskType());
        if (TaskType.UNKNOWN_JOB == jobType) {
            if (jobOverviewJobVo.getJobClass() != null
                    && jobOverviewJobVo.getJobClass().indexOf("SaturnScriptJob") != -1) {
                jobOverviewJobVo.setJobType(TaskType.SHELL_JOB.name());
            } else {
                jobOverviewJobVo.setJobType(TaskType.JAVA_JOB.name());
            }
        }
    }

    protected List getJobSubListByPage(List unSystemJobs, Pageable pageable) {
        int totalCount = unSystemJobs.size();
        int offset = pageable.getOffset();
        int end = offset + pageable.getPageSize();
        int fromIndex = totalCount >= offset ? offset : -1;
        int toIndex = totalCount >= end ? end : totalCount;
        if (fromIndex == -1 || fromIndex > toIndex) {
            return Lists.newArrayList();
        }
        return unSystemJobs.subList(fromIndex, toIndex);
    }

    private JobOverviewVo countJobOverviewVo(String namespace) throws RuntimeException {
        JobOverviewVo jobOverviewVo = new JobOverviewVo();
        jobOverviewVo.setTotalNumber(
                taskService.countUnSystemJobsWithCondition(namespace, Maps.<String, Object>newHashMap()));
        jobOverviewVo.setEnabledNumber(taskService.countEnabledUnSystemJobs(namespace));
        // 获取该域下的异常作业数量，捕获所有异常，打日志，不抛到前台
        updateAbnormalJobSizeInOverview(namespace, jobOverviewVo);
        return jobOverviewVo;
    }


    //@ApiResponses(value = {@ApiResponse(code = 200, message = "Success/Fail", response = RequestResult.class)})
    //@Audit
    @PostMapping(value = "/{jobName}/enable")
    public Response enableJob(final HttpServletRequest request,
                              @PathVariable String namespace,
                              @PathVariable String jobName) throws RuntimeException {
        //assertIsPermitted(PermissionKeys.jobEnable, namespace);
        taskService.enableJob(namespace, jobName, getCurrentLoginUserName());
        return new Response();
    }

    //@ApiResponses(value = {@ApiResponse(code = 200, message = "Success/Fail", response = RequestResult.class)})
    //@Audit
    @PostMapping(value = "/enable")
    public Response batchEnableJob(final HttpServletRequest request,
                                   @PathVariable String namespace,
                                   @RequestParam List<String> jobNames) throws RuntimeException {
        //assertIsPermitted(PermissionKeys.jobBatchEnable, namespace);
        String userName = null;// getCurrentLoginUserName();
        for (String jobName : jobNames) {
            taskService.enableJob(namespace, jobName, userName);
        }
        return new Response();
    }

    //@ApiResponses(value = {@ApiResponse(code = 200, message = "Success/Fail", response = RequestResult.class)})
    //@Audit
    @PostMapping(value = "/{jobName}/disable")
    public Response disableJob(final HttpServletRequest request,
                               @PathVariable String namespace,
                               @PathVariable String jobName) throws RuntimeException {
        //assertIsPermitted(PermissionKeys.jobDisable, namespace);
        taskService.disableJob(namespace, jobName, getCurrentLoginUserName());
        return new Response();
    }

    private String getCurrentLoginUserName() {
        return null;//
    }

    //@ApiResponses(value = {@ApiResponse(code = 200, message = "Success/Fail", response = RequestResult.class)})
    //@Audit
    @PostMapping(value = "/disable")
    public Response batchDisableJob(final HttpServletRequest request,
                                    @PathVariable String namespace,
                                    @RequestParam List<String> jobNames) throws RuntimeException {
        //assertIsPermitted(PermissionKeys.jobBatchDisable, namespace);
        String userName = getCurrentLoginUserName();
        for (String jobName : jobNames) {
            taskService.disableJob(namespace, jobName, userName);
        }
        return new Response();
    }

    //@ApiResponses(value = {@ApiResponse(code = 200, message = "Success/Fail", response = RequestResult.class)})
    //@Audit
    @DeleteMapping(value = "/{jobName}")
    public Response removeJob(final HttpServletRequest request,
                              @PathVariable String namespace,
                              @PathVariable String jobName) throws RuntimeException {
        //assertIsPermitted(PermissionKeys.jobRemove, namespace);
        taskService.removeTask(namespace, jobName);
        return new Response();
    }

    //@ApiResponses(value = {@ApiResponse(code = 200, message = "Success/Fail", response = RequestResult.class)})
    //@Audit
    @DeleteMapping
    public Response batchRemoveJob(final HttpServletRequest request,
                                   @PathVariable String namespace,
                                   @RequestParam List<String> jobNames) throws RuntimeException {
        //assertIsPermitted(PermissionKeys.jobBatchRemove, namespace);
        List<String> successJobNames = new ArrayList<>();
        List<String> failJobNames = new ArrayList<>();
        for (String jobName : jobNames) {
            try {
                taskService.removeTask(namespace, jobName);
                successJobNames.add(jobName);
            } catch (Exception e) {
                failJobNames.add(jobName);
                log.info("remove job failed", e);
            }
        }
        if (!failJobNames.isEmpty()) {
            StringBuilder message = new StringBuilder();
            message.append("删除成功的作业:").append(successJobNames).append("，").append("删除失败的作业:").append(failJobNames);
            throw new RuntimeException(message.toString());
        }
        return new Response();
    }

    /**
     * 批量设置作业的优先Executor
     */
    //@ApiResponses(value = {@ApiResponse(code = 200, message = "Success/Fail", response = RequestResult.class)})
    //@Audit
    @PostMapping(value = "/preferExecutors")
    public Response batchSetPreferExecutors(final HttpServletRequest request,
                                            @PathVariable String namespace,
                                            @RequestParam List<String> jobNames,
                                            @RequestParam String preferList) throws RuntimeException {
        //assertIsPermitted(PermissionKeys.jobBatchSetPreferExecutors, namespace);
        String userName = getCurrentLoginUserName();
        for (String jobName : jobNames) {
            taskService.setPreferList(namespace, jobName, preferList, userName);
        }
        return new Response();
    }


    //@ApiResponses(value = {@ApiResponse(code = 200, message = "Success/Fail", response = RequestResult.class)})
    //@Audit
    @PostMapping(value = "/jobs")
    public Response createJob(final HttpServletRequest request,
                              @PathVariable String namespace, TaskConfiguration taskConfiguration)
            throws RuntimeException {
        //assertIsPermitted(PermissionKeys.jobAdd, namespace);
        taskService.addJob(namespace, taskConfiguration, getCurrentLoginUserName());
        return new Response();
    }


    //@ApiResponses(value = {@ApiResponse(code = 200, message = "Success/Fail", response = RequestResult.class)})
    //@Audit
    @PostMapping(value = "/import")
    public Response importJobs(final HttpServletRequest request,
                               @PathVariable String namespace, @RequestParam("file") MultipartFile file)
            throws RuntimeException {
        //assertIsPermitted(PermissionKeys.jobImport, namespace);
        if (file.isEmpty()) {
            throw new RuntimeException("请上传一个有内容的文件");
        }
        String originalFilename = file.getOriginalFilename();
        if (originalFilename == null || !originalFilename.endsWith(".xls")) {
            throw new RuntimeException("仅支持.xls文件导入");
        }
        //AuditInfoContext.put("originalFilename", originalFilename);
        return Response.success(taskService.importTasks(namespace, file, getCurrentLoginUserName()));
    }

    // 导出全部的作业
    //@ApiResponses(value = {@ApiResponse(code = 200, message = "Success/Fail", response = RequestResult.class)})
    //@Audit
    @GetMapping(value = "/export")
    public void exportJobs(final HttpServletRequest request, @PathVariable String namespace,
                           final HttpServletResponse response) throws RuntimeException {
        File exportJobFile = taskService.exportJobs(namespace);
        String currentTime = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
        String exportFileName = namespace + "_allJobs_" + currentTime + ".xls";
        SaturnConsoleUtils.exportFile(response, exportJobFile, exportFileName, true);
    }

    // 导出选定的作业
    //@ApiResponses(value = {@ApiResponse(code = 200, message = "Success/Fail", response = RequestResult.class)})
    //@Audit
    @GetMapping(value = "/exportSelected")
    public void exportSelectedJobs(final HttpServletRequest request, @PathVariable String namespace,
                                   @RequestParam(required = false) List<String> jobList, final HttpServletResponse response) throws RuntimeException {
        // assertIsPermitted(PermissionKeys.jobExport, namespace);
        File exportJobFile = taskService.exportSelectedJobs(namespace, jobList);
        String currentTime = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
        String exportFileName = namespace + "_" + currentTime + ".xls";
        SaturnConsoleUtils.exportFile(response, exportJobFile, exportFileName, true);
    }


    /**
     * 获取该作业可选择的优先Executor
     */
    //@ApiResponses(value = {@ApiResponse(code = 200, message = "Success/Fail", response = RequestResult.class)})
    @GetMapping(value = "/{jobName}/executors")
    public Response getExecutors(final HttpServletRequest request, @PathVariable String namespace,
                                 @PathVariable String jobName) throws RuntimeException {
        return Response.success(taskService.getExecutorProvidedList(namespace, jobName));
    }


    //@ApiResponses(value = {@ApiResponse(code = 200, message = "Success/Fail", response = RequestResult.class)})
    //@Audit
    @PostMapping("/batchRunAtOnce")
    public Response batchRunAtOnce(@PathVariable String namespace,
                                   @RequestParam List<String> jobNames) throws RuntimeException {
        //assertIsPermitted(PermissionKeys.jobBatchRunAtOnce, namespace);
        if (!CollectionUtils.isEmpty(jobNames)) {
            for (String jobName : jobNames) {
                taskService.runAtOnce(namespace, jobName);
            }
        }
        return new Response();
    }

}
